package com.derbysoft.dswitch.adapter.ihg.dswitch2.service.impl;

import com.derbysoft.dswitch.adapter.ihg.common.domain.ChannelAuthorization;
import com.derbysoft.dswitch.adapter.ihg.common.domain.ReservationItem;
import com.derbysoft.dswitch.adapter.ihg.common.domain.Status;
import com.derbysoft.dswitch.adapter.ihg.common.log.LogBody;
import com.derbysoft.dswitch.adapter.ihg.common.log.PerformanceLogger;
import com.derbysoft.dswitch.adapter.ihg.common.repository.ChannelAuthorizationRepository;
import com.derbysoft.dswitch.adapter.ihg.common.repository.ReservationItemRepository;
import com.derbysoft.dswitch.adapter.ihg.common.repository.ReservationRepository;
import com.derbysoft.dswitch.adapter.ihg.common.utils.EnvironmentUtils;
import com.derbysoft.dswitch.adapter.ihg.dswitch2.HotelProviderService;
import com.derbysoft.dswitch.adapter.ihg.dswitch2.translate.BaseTranslator;
import com.derbysoft.dswitch.adapter.ihg.dswitch3.translate.Translator;
import com.derbysoft.dswitch.adapter.ihg.dto.*;
import com.derbysoft.dswitch.adapter.ihg.dto.Error;
import com.derbysoft.dswitch.adapter.ihg.exception.ChannelNotFoundException;
import com.derbysoft.dswitch.adapter.ihg.exception.ConfirmNumberNotFoundException;
import com.derbysoft.dswitch.adapter.ihg.webservice.handler.WebServiceHandler;
import com.derbysoft.dswitch.adapter.ihg.webservice.impl.AccountProvider;
import com.derbysoft.remote.dswitch.core.*;
import com.derbysoft.remote.dswitch.generic.constants.TransactionStatusType;
import com.derbysoft.remote.dswitch.generic.constants.UniqueIDType;
import com.derbysoft.remote.dswitch.generic.dto.UniqueIDDTO;
import com.derbysoft.remote.dswitch.hotel.dto.CancelInfoDTO;
import com.derbysoft.remote.dswitch.hotel.dto.CancelRQ;
import com.derbysoft.remote.dswitch.hotel.dto.CancelRS;
import com.derbysoft.remote.dswitch.hotel.dto.HotelReservationIDDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("ds2cancelWebService")
public class CancelWebServiceImpl extends BaseHotelWebService
        implements HotelProviderService<GenericRQ<CancelRQ>, GenericRS<CancelRS>> {
    @Autowired
    private ReservationItemRepository reservationItemRepository;

    @Autowired
    @Qualifier("bookWebServiceHandler")
    private WebServiceHandler<OTACancelRQ, OTACancelRS> cancelWebService;

    @Autowired
    private ReservationRepository reservationRepository;

    @Autowired
    protected ChannelAuthorizationRepository channelAuthorizationRepository;

    @Override
    public GenericRS<CancelRS> invoke(GenericRQ<CancelRQ> request) {
        try {
            HotelReservationIDDTO erspID = ReservationIDUtils.getErspNo(request.getPayload().getHotelReservationIDs());
            OTACancelRQ otaCancelRQ = translateCancelRequest(request, findConfirmNo(erspID));
            String derbyChannelCode = request.getHeader().getSource().getUniqueID().getId();
            OTACancelRS otaCancelRS = cancelWebService.invoke(otaCancelRQ, true, AccountProvider.of(EnvironmentUtils.getIhgCancelUrl(),
                    loadAuthorization(derbyChannelCode)));

            GenericRS<CancelRS> cancelRS = createEmptyCancelRS(request);
            if (otaCancelRS.getErrors() != null) {
                reservationRepository.updateStatus(erspID.getResIDValue(), Status.CANCEL_FAILED);
                cancelRS.setErrors(createCancelFailedError(otaCancelRS));
                return cancelRS;
            }
            cancelRS.setSuccess(new SuccessDTO());
            reservationRepository.updateStatus(erspID.getResIDValue(), Status.CANCELLED);
            String cancelledNumber = otaCancelRS.getCancelInfoRS().getUniqueID().getID();
            reservationRepository.cancelReservationItem(erspID.getResIDValue(), cancelledNumber);
            cancelRS.setPayload(createCancelRS(request, cancelledNumber));
            return cancelRS;
        } catch (Exception ex) {
            GenericRS<CancelRS> failedResponse = failedResponse(request, ex);
            PerformanceLogger.append(LogBody.ERROR_CODE, failedResponse.getErrors().getErrorList().get(0).getCode());
            return failedResponse;
        }
    }

    private GenericRS<CancelRS> failedResponse(GenericRQ<CancelRQ> request, Exception ex) {
        GenericRS<CancelRS> response = new GenericRS<CancelRS>();
        ResponseHeader header = new ResponseHeader();
        header.setTaskId(request.getHeader().getTaskId());
        response.setHeader(header);
        response.setErrors(createErrorDTO(ex));
        return response;
    }

    private ErrorsDTO createCancelFailedError(OTACancelRS otaCancelRS) {
        ErrorsDTO errorsDTO = new ErrorsDTO();
        ErrorDTO errorDTO = new ErrorDTO();
        errorDTO.setCode(otaCancelRS.getErrors().getError().get(0).getCode() == null ?
                SYSTEM : otaCancelRS.getErrors().getError().get(0).getCode());
        errorDTO.setSource(provider);
        StringBuilder builder = new StringBuilder();
        for (Error error : otaCancelRS.getErrors().getError()) {
            builder.append(error.getShortText()).append(" : ");
        }
        errorDTO.setMessage(builder.toString());
        errorsDTO.getErrorList().add(errorDTO);
        return errorsDTO;
    }

    private GenericRS<CancelRS> createEmptyCancelRS(GenericRQ<CancelRQ> cancelRQ) {
        GenericRS<CancelRS> cancelRS = new GenericRS<CancelRS>();
        ResponseHeader responseHeader = new ResponseHeader();
        responseHeader.setTaskId(cancelRQ.getHeader().getTaskId());
        cancelRS.setHeader(responseHeader);
        return cancelRS;
    }

    private String findConfirmNo(HotelReservationIDDTO hotelReservationIDDTO) {
        ReservationItem reservationItem = reservationItemRepository.loadByErsp(hotelReservationIDDTO.getResIDValue());
        if (reservationItem == null || reservationItem.getConfirmNumber() == null) {
            throw new ConfirmNumberNotFoundException("ersp :" + hotelReservationIDDTO.getResIDValue());
        }
        return reservationItem.getConfirmNumber();
    }

    private OTACancelRQ translateCancelRequest(GenericRQ<CancelRQ> request, String confirmNumber) {
        OTACancelRQ cancelRequest = new OTACancelRQ();
        cancelRequest.setCancelType("Cancel");
        UniqueId uniqueId10 = new UniqueId();
        uniqueId10.setID(confirmNumber);
        uniqueId10.setType("10");
        uniqueId10.setInstance("PNR");
        cancelRequest.getUniqueID().add(uniqueId10);

        cancelRequest.setPOS(createPOS(request.getHeader().getSource().getUniqueID().getId()));
        cancelRequest.setVersion(Translator.VERSION);
        return cancelRequest;
    }

    private CancelRS createCancelRS(GenericRQ<CancelRQ> request, String cancelledNumber) {
        CancelRS cancelRS = new CancelRS();
        cancelRS.setResStatusType(TransactionStatusType.CANCELLED);
        cancelRS.setHotelReservationIDs(request.getPayload().getHotelReservationIDs());
        cancelRS.setCancelInfo(new CancelInfoDTO(new UniqueIDDTO(cancelledNumber, UniqueIDType.HOTEL)));
        return cancelRS;
    }

    private POS createPOS(String derbyChannelCode) {
        POS pos = new POS();
        Source source = new Source();
        source.setRequestorID(createRequestId(derbyChannelCode));
        pos.setSource(source);
        return pos;
    }

    private RequestorId createRequestId(String derbyChannelCode) {
        RequestorId requestorId = new RequestorId();
        ChannelAuthorization channelAuthorization = channelAuthorizationRepository.loadByDerbyChannelCode(derbyChannelCode.toLowerCase());
        if (channelAuthorization == null) {
            throw new ChannelNotFoundException(derbyChannelCode);
        }
        requestorId.setID(channelAuthorization.getIataNumber());
        requestorId.setType(BaseTranslator.REQUEST_TYPE);
        return requestorId;
    }
}
